/*
 * Author: Leazer
 * Create: 2021/11/2
 */
package main

import (
	"errors"
	"fmt"
	"net/http"
	"punch/dbs"
	"punch/routers"
	"punch/utils"
	"time"

	jwt "github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"

	_ "github.com/go-sql-driver/mysql"
)

const (
	ErrorServerBusy = "server is busy"
	ErrorReLogin    = "relogin"
)

type JWTClaims struct {
	jwt.StandardClaims
	UserID   int    `json:"user_id"`
	Usertype string `json:"user_type"`
	Password string `json:"password"`
	Username string `json:"username"`
}

var (
	Secret     = "123#111" //salt
	ExpireTime = 3600      //token expire time
)

// 每个接口的权限根据解析token中的type划分，在每个函数开始部分进行操作拦截
func main() {
	r := gin.Default()
	// 不经过中间件的路由定义在这
	r.POST("/punc/manager/login", managerLoginHandler)
	r.POST("/punc/teacher/login", teacherLoginHandler)
	r.POST("/punc/student/login", studentLoginHandler)

	// 使用中间件
	r.Use(MiddleWare())

	// 经过中间件的路由定义子在这
	routers.LoadUser(r)
	routers.LoadOrg(r)
	routers.LoadManager(r)
	routers.LoadTeacher(r)
	routers.LoadStudent(r)

	// 运行
	if err := r.Run(); err != nil {
		fmt.Println(err)
	}
}

// 定义数据表结构体
type Manager struct {
	Id          int    `db:"id"`
	Name        string `db:"name"`
	Account     string `db:"account"`
	Password    string `db:"password"`
	Create_time string `db:"create_time"`
	Update_time string `db:"update_time"`
}

type Teacher struct {
	Id          int    `db:"id"`
	Org_id      int    `db:"org_id"`
	Name        string `db:"name"`
	Phone       string `db:"phone"`
	Email       string `db:"email"`
	Duty        string `db:"duty"`
	Tno         string `db:"tno"`
	Password    string `db:"password"`
	Type        int    `db:"type"`
	Create_time string `db:"create_time"`
	Update_time string `db:"update_time"`
}

type Student struct {
	Id          int    `db:"id"`
	Org_id      int    `db:"org_id"`
	Name        string `db:"name"`
	Sex         string `db:"sex"`
	Phone       string `db:"phone"`
	Email       string `db:"email"`
	Class       string `db:"class"`
	Apartment   string `db:"apartment"`
	Avatar      string `db:"avatar"`
	Note        string `db:"note"`
	Credit      int    `db:"credit"`
	Points      int    `db:"points"`
	Peract_repu string `db:"peract_repu"`
	Triact_repu string `db:"triact_repu"`
	Sno         string `db:"sno"`
	Password    string `db:"password"`
	Type        int    `db:"type"`
	Create_time string `db:"create_time"`
	Update_time string `db:"update_time"`
}

// 定义接收数据的结构体
type ManagerParamsLogin struct {
	Account  string `json:"account" binding:"required"`
	Password string `json:"password" binding:"required"`
}

type TeacherParamsLogin struct {
	Tno      string `json:"tno" binding:"required"`
	Password string `json:"password" binding:"required"`
}

type StudentParamsLogin struct {
	Sno      string `json:"sno" binding:"required"`
	Password string `json:"password" binding:"required"`
}

// 管理员登录
func managerLoginHandler(c *gin.Context) {
	var params ManagerParamsLogin
	if err := c.ShouldBindJSON(&params); err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": 20003,
			"msg":  err.Error(),
		})
		return
	}

	db, err := dbs.Conn(c)
	if err != nil {
		db.Close()
		return
	}
	var manager []Manager
	err = db.Select(&manager, "select * from manager where account=?", params.Account)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": 20004,
			"msg":  err.Error(),
		})
		return
	}
	if len(manager) == 0 {
		c.JSON(http.StatusOK, gin.H{
			"code": 20006,
			"msg":  "未找到该用户",
		})
		return
	}
	if utils.GetMD5(params.Password) != manager[0].Password {
		c.JSON(http.StatusOK, gin.H{
			"code": 20005,
			"msg":  "密码不正确",
		})
		return
	}

	claims := &JWTClaims{
		UserID:   manager[0].Id,
		Usertype: "manager",
		Username: params.Account,
		Password: params.Password,
	}
	claims.IssuedAt = time.Now().Unix()
	claims.ExpiresAt = time.Now().Add(time.Second * time.Duration(ExpireTime)).Unix()
	singedToken, err := genToken(claims)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{
			"code": 20006,
			"msg":  err.Error(),
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"code": 20000,
		"msg":  "登陆成功",
		"data": gin.H{
			"Name":        manager[0].Name,
			"Account":     manager[0].Account,
			"User_type":   "manager",
			"Create_time": manager[0].Create_time,
			"Update_time": manager[0].Update_time,
			"Token":       singedToken,
		},
	})

	db.Close()
}

// 教师登录
func teacherLoginHandler(c *gin.Context) {
	var params TeacherParamsLogin
	if err := c.ShouldBindJSON(&params); err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": 20003,
			"msg":  err.Error(),
		})
		return
	}

	db, err := dbs.Conn(c)
	if err != nil {
		db.Close()
		return
	}
	var teacher []Teacher
	err = db.Select(&teacher, "select * from teacher where tno=?", params.Tno)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": 20004,
			"msg":  err.Error(),
		})
		return
	}
	if len(teacher) == 0 {
		c.JSON(http.StatusOK, gin.H{
			"code": 20006,
			"msg":  "未找到该用户",
		})
		return
	}
	if utils.GetMD5(params.Password) != teacher[0].Password {
		c.JSON(http.StatusOK, gin.H{
			"code": 20005,
			"msg":  "密码不正确",
		})
		return
	}

	claims := &JWTClaims{
		UserID:   teacher[0].Id,
		Usertype: "teacher",
		Username: params.Tno,
		Password: params.Password,
	}
	claims.IssuedAt = time.Now().Unix()
	claims.ExpiresAt = time.Now().Add(time.Second * time.Duration(ExpireTime)).Unix()
	singedToken, err := genToken(claims)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{
			"code": 20006,
			"msg":  err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"code": 20000,
		"msg":  "登陆成功",
		"data": gin.H{
			"Name":        teacher[0].Name,
			"Tno":         teacher[0].Tno,
			"User_type":   "teacher",
			"Create_time": teacher[0].Create_time,
			"Update_time": teacher[0].Update_time,
			"Token":       singedToken,
		},
	})

	db.Close()
}

// 学生登陆
func studentLoginHandler(c *gin.Context) {
	var params StudentParamsLogin
	if err := c.ShouldBindJSON(&params); err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": 20003,
			"msg":  err.Error(),
		})
		return
	}

	db, err := dbs.Conn(c)
	if err != nil {
		db.Close()
		return
	}
	var student []Student
	err = db.Select(&student, "select * from student where sno=?", params.Sno)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": 20004,
			"msg":  err.Error(),
		})
		return
	}
	if len(student) == 0 {
		c.JSON(http.StatusOK, gin.H{
			"code": 20006,
			"msg":  "未找到该用户",
		})
		return
	}
	if utils.GetMD5(params.Password) != student[0].Password {
		c.JSON(http.StatusOK, gin.H{
			"code": 20005,
			"msg":  "密码不正确",
		})
		return
	}

	claims := &JWTClaims{
		UserID:   student[0].Id,
		Usertype: "student",
		Username: params.Sno,
		Password: params.Password,
	}
	claims.IssuedAt = time.Now().Unix()
	// 学生登录过期时间为30天
	claims.ExpiresAt = time.Now().Add(time.Second * time.Duration(2592000)).Unix()
	singedToken, err := genToken(claims)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{
			"code": 20006,
			"msg":  err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"code": 20000,
		"msg":  "登陆成功",
		"data": gin.H{
			"Name":        student[0].Name,
			"Sno":         student[0].Sno,
			"User_type":   "student",
			"Create_time": student[0].Create_time,
			"Update_time": student[0].Update_time,
			"Token":       singedToken,
		},
	})

	db.Close()
}

// 定义中间件，这里的中间件首先校验token是否有效，然后再根据token中的用户类型分别校验此用户在数据库中是否存在
func MiddleWare() gin.HandlerFunc {
	return func(c *gin.Context) {
		claim, err := verifyAction(c.Request.Header.Get("X-token"))
		if err != nil {
			c.JSON(http.StatusOK, gin.H{
				"code": 20001,
				"msg":  "token无效，请重新登录",
			})
			c.Abort()
			return
		}

		// 检验数据库中是否存在该用户
		db, err := dbs.Conn(c)
		if err != nil {
			db.Close()
			c.Abort()
			return
		}

		switch claim.Usertype {
		case "manager":
			var manager []Manager
			err := db.Select(&manager, "select id from manager where id=?", claim.UserID)
			if err != nil {
				c.JSON(http.StatusOK, gin.H{
					"code": 20001,
					"msg":  "token无效，请重新登录",
				})
				c.Abort()
				return
			}
			if len(manager) == 0 {
				c.JSON(http.StatusOK, gin.H{
					"code": 20001,
					"msg":  "token无效，请重新登录",
				})
				c.Abort()
				return
			}
			c.Set("UserOrgID", 0)
		case "teacher":
			var teacher []Teacher
			err := db.Select(&teacher, "select id, org_id from teacher where id=?", claim.UserID)
			if err != nil {
				c.JSON(http.StatusOK, gin.H{
					"code": 20001,
					"msg":  "token无效，请重新登录",
				})
				c.Abort()
				return
			}
			if len(teacher) == 0 {
				c.JSON(http.StatusOK, gin.H{
					"code": 20001,
					"msg":  "token无效，请重新登录",
				})
				c.Abort()
				return
			}
			c.Set("UserOrgID", teacher[0].Org_id)
		case "student":
			var student []Student
			err := db.Select(&student, "select id, org_id from student where id=?", claim.UserID)
			if err != nil {
				c.JSON(http.StatusOK, gin.H{
					"code": 20001,
					"msg":  "token无效，请重新登录",
				})
				c.Abort()
				return
			}
			if len(student) == 0 {
				c.JSON(http.StatusOK, gin.H{
					"code": 20001,
					"msg":  "token无效，请重新登录",
				})
				c.Abort()
				return
			}
			c.Set("UserOrgID", student[0].Org_id)
		default:
			c.JSON(http.StatusOK, gin.H{
				"code": 20001,
				"msg":  "token无效，请重新登录",
			})
			c.Abort()
			return
		}

		c.Set("UserID", claim.UserID)
		c.Set("Usertype", claim.Usertype)
		c.Set("Username", claim.Username)
		c.Set("Password", claim.Password)

		db.Close()
	}
}

//生成 jwt token
func genToken(claims *JWTClaims) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	signedToken, err := token.SignedString([]byte(Secret))
	if err != nil {
		return "", errors.New(ErrorServerBusy)
	}
	return signedToken, nil
}

//验证jwt token
func verifyAction(strToken string) (*JWTClaims, error) {
	token, err := jwt.ParseWithClaims(strToken, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(Secret), nil
	})
	if err != nil {
		return nil, errors.New(ErrorServerBusy)
	}

	claims, ok := token.Claims.(*JWTClaims)
	if !ok {
		return nil, errors.New(ErrorReLogin)
	}
	if err := token.Claims.Valid(); err != nil {
		return nil, errors.New(ErrorReLogin)
	}

	return claims, nil
}

// func refresh(c *gin.Context) {
// 	strToken := c.Request.Header.Get("X-token")
// 	claims, err := verifyAction(strToken)
// 	if err != nil {
// 		c.String(http.StatusNotFound, err.Error())
// 		return
// 	}
// 	claims.ExpiresAt = time.Now().Unix() + (claims.ExpiresAt - claims.IssuedAt)
// 	signedToken, err := genToken(claims)
// 	if err != nil {
// 		c.String(http.StatusNotFound, err.Error())
// 		return
// 	}
// 	c.String(http.StatusOK, signedToken, ", ", claims.ExpiresAt)
// }
